/******************************************************************
 * 个人博客：https://blog.csdn.net/2302_80277720
 * 嵌入式Linux学习交流群：1005210698
 * 欢迎各位大佬和萌新来加入交流学习
 * Change Logs:
 * Date           Author       Notes
 * 2025-08-24     喝呜昂黄    first version
 * 2025-08-30     喝呜昂黄    修改页翻转，添加日志功能
 ******************************************************************/

#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/poll.h>
#include <unistd.h>

#include <linux/videodev2.h>

#include "video_manager.h"

static video_opr_t usb_camera_opr;

static int usb_query_capability(video_dev_t *video_dev) {
    int ret = 0;
    struct v4l2_capability cap;
    CLEAR(cap);

    ret = ioctl(video_dev->fd, VIDIOC_QUERYCAP, &cap);
    if (ret != 0) {
        MEDIA_LOG_ERROR(" VIDIOC_QUERYCAP failed\n");
        return -1;
    }

    MEDIA_LOG_INFO("------usb query capability------\n");
    MEDIA_LOG_INFO("driver = %s\n", cap.driver);
    MEDIA_LOG_INFO("card = %s\n", cap.card);
    MEDIA_LOG_INFO("bus_info = %s\n", cap.bus_info);
    MEDIA_LOG_INFO("version = %u.%u.%u\n", (cap.version >> 16) & 0xFF, (cap.version >> 8) & 0xFF, cap.version & 0xFF);
    MEDIA_LOG_INFO("capability = 0x%x\n", cap.capabilities);
    MEDIA_LOG_INFO("device_caps = 0x%x\n", cap.device_caps);
    MEDIA_LOG_INFO("---------------------------------\n");

    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
        MEDIA_LOG_WARN("该设备不支持视频采集设备\n");
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE))
        MEDIA_LOG_WARN("该设备不支持多平面格式\n");

    return ret;
}

static int usb_enum_formats(video_dev_t *video_dev) {
    int index = 0;
    struct v4l2_fmtdesc fmtdesc;

    CLEAR(fmtdesc);
    fmtdesc.index = 0;
    fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    MEDIA_LOG_INFO("--------usb enum formats--------\n");
    while (!ioctl(video_dev->fd, VIDIOC_ENUMINPUT, &fmtdesc)) {
        fmtdesc.index = index++;

        MEDIA_LOG_INFO("index = %d\n", fmtdesc.index);
        MEDIA_LOG_INFO("type = %d\n", fmtdesc.type);
        MEDIA_LOG_INFO("flags = 0x%x\n", fmtdesc.flags);
        MEDIA_LOG_INFO("description = %s\n", fmtdesc.description);
        MEDIA_LOG_INFO("pixelformat = %c.%c.%c.%c\n\n", fmtdesc.pixelformat & 0xFF, (fmtdesc.pixelformat >> 8) & 0xFF,
            (fmtdesc.pixelformat >> 16) & 0xFF, (fmtdesc.pixelformat >> 24) & 0xFF);
    }
    MEDIA_LOG_INFO("---------------------------------\n");

    return 0;
}

static void usb_munmap(video_dev_t *video_dev) {
    for (int i = 0; i < BUF_COUNT; i++) munmap(video_dev->mmap_buf[i].virt_addr, video_dev->buf_size);
}

static int usb_set_format(video_dev_t *video_dev) {
    int ret;
    struct v4l2_format fmt;
    CLEAR(fmt);

    if (!(video_dev->width) || !(video_dev->height) || !(video_dev->pixelformat)) {
        MEDIA_LOG_ERROR("请设置摄像头宽、高、像素格式\n");
        return -1;
    }

    fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width       = video_dev->width;
    fmt.fmt.pix.height      = video_dev->height;
    fmt.fmt.pix.pixelformat = video_dev->pixelformat;
    fmt.fmt.pix.field       = V4L2_FIELD_ANY;

    ret = ioctl(video_dev->fd, VIDIOC_S_FMT, &fmt);
    if (ret < 0) {
        MEDIA_LOG_ERROR("VIDIOC_S_FMT failed\n");
        return -1;
    }

#ifdef CAM_DEBUG
    MEDIA_LOG_INFO("--------usb set format--------\n");
    MEDIA_LOG_INFO("type = %d\n", fmt.type);
    MEDIA_LOG_INFO("width = %d\n", fmt.fmt.pix.width);
    MEDIA_LOG_INFO("height = %d\n", fmt.fmt.pix.height);
    MEDIA_LOG_INFO("pixelformat = %c.%c.%c.%c\n", fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
        (fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
    MEDIA_LOG_INFO("field = 0x%x\n", fmt.fmt.pix.field);
    MEDIA_LOG_INFO("bytesperline = %d\n", fmt.fmt.pix.bytesperline);
    MEDIA_LOG_INFO("sizeimage = %d\n", fmt.fmt.pix.sizeimage);
    MEDIA_LOG_INFO("colorspace = %d\n", fmt.fmt.pix.colorspace);
    MEDIA_LOG_INFO("priv = 0x%x\n", fmt.fmt.pix.priv);
    if (fmt.fmt.pix.priv == V4L2_PIX_FMT_PRIV_MAGIC) {
        MEDIA_LOG_INFO("flags = 0x%x\n", fmt.fmt.pix.flags);
        MEDIA_LOG_INFO("ycbcr_enc = %d\n", fmt.fmt.pix.ycbcr_enc);
        MEDIA_LOG_INFO("quantization = %d\n", fmt.fmt.pix.quantization);
        MEDIA_LOG_INFO("xfer_func = %d\n", fmt.fmt.pix.xfer_func);
    }
    MEDIA_LOG_INFO("---------------------------------\n");
#endif

    return 0;
}

static int usb_get_format(video_dev_t *video_dev) {
    int ret;
    struct v4l2_format fmt;

    CLEAR(fmt);
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    ret = ioctl(video_dev->fd, VIDIOC_G_FMT, &fmt);
    if (ret < 0) {
        MEDIA_LOG_ERROR("VIDIOC_G_FMT failed\n");
        return -1;
    }
    MEDIA_LOG_INFO("======== after VIDIOC_G_FMT ========\n");
    MEDIA_LOG_INFO("type = %d\n", fmt.type);
    MEDIA_LOG_INFO("width = %d\n", fmt.fmt.pix_mp.width);
    MEDIA_LOG_INFO("height = %d\n", fmt.fmt.pix_mp.height);
    MEDIA_LOG_INFO("pixelformat = %c.%c.%c.%c\n", fmt.fmt.pix_mp.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
        (fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
    MEDIA_LOG_INFO("field = 0x%x\n", fmt.fmt.pix_mp.field);

    MEDIA_LOG_INFO("bytesperline = %d\n", fmt.fmt.pix.bytesperline);
    MEDIA_LOG_INFO("sizeimage = %d\n", fmt.fmt.pix.sizeimage);
    MEDIA_LOG_INFO("colorspace = %d\n", fmt.fmt.pix.colorspace);
    MEDIA_LOG_INFO("priv = 0x%x\n", fmt.fmt.pix.priv);
    if (fmt.fmt.pix.priv == V4L2_PIX_FMT_PRIV_MAGIC) {
        MEDIA_LOG_INFO("flags = 0x%x\n", fmt.fmt.pix.flags);
        MEDIA_LOG_INFO("ycbcr_enc = %d\n", fmt.fmt.pix.ycbcr_enc);
        MEDIA_LOG_INFO("quantization = %d\n", fmt.fmt.pix.quantization);
        MEDIA_LOG_INFO("xfer_func = %d\n", fmt.fmt.pix.xfer_func);
    }

    MEDIA_LOG_INFO("num_plans = %d\n", fmt.fmt.pix_mp.num_planes);
    MEDIA_LOG_INFO("colorspace = 0x%x\n", fmt.fmt.pix_mp.colorspace);
    MEDIA_LOG_INFO("num_planes = %d\n", fmt.fmt.pix_mp.num_planes);
    for (int i = 0; i < fmt.fmt.pix_mp.num_planes; ++i) {
        MEDIA_LOG_INFO(
            "plane[%d]: sizeimage = %d bytesperline = %d\n", i, fmt.fmt.pix_mp.plane_fmt[i].sizeimage, fmt.fmt.pix_mp.plane_fmt[i].bytesperline);
    }
    MEDIA_LOG_INFO("====================================\n\n");

    return 0;
}

static int usb_request_buffers(video_dev_t *video_dev) {
    int ret;
    struct v4l2_requestbuffers rb;

    CLEAR(rb);
    rb.count = BUF_COUNT;
    rb.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;

#ifdef MMAP_BUF
    rb.memory = V4L2_MEMORY_MMAP;
#elif DMABUF_BUF
    rb.memory = V4L2_MEMORY_DMABUF;
#endif

    ret = ioctl(video_dev->fd, VIDIOC_REQBUFS, &rb);
    if (ret < 0) {
        MEDIA_LOG_ERROR("VIDIOC_REQBUFS failed\n");
        return -1;
    }

#ifdef CAM_DEBUG
    MEDIA_LOG_INFO("--------usb request buffers--------\n");
    MEDIA_LOG_INFO("type = %d\n", rb.type);
    MEDIA_LOG_INFO("count = %d\n", rb.count);
    MEDIA_LOG_INFO("memory = %d\n", rb.memory);
    MEDIA_LOG_INFO("---------------------------------\n");
#endif

    return 0;
}

static int usb_mmap_buffers(video_dev_t *video_dev) {
    int ret;
    struct v4l2_buffer buffer;

    CLEAR(buffer);
    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
#ifdef MMAP_BUF
    buffer.memory = V4L2_MEMORY_MMAP;
#elif DMA_BUF
    buffer.memory = V4L2_MEMORY_DMABUF;
#endif

    for (int i = 0; i < BUF_COUNT; i++) {
        buffer.index = i;

        ret = ioctl(video_dev->fd, VIDIOC_QUERYBUF, &buffer);
        if (ret < 0) {
            MEDIA_LOG_ERROR("VIDIOC_QUERYBUF failed\n");
            return -1;
        }
#ifdef MMAP_BUF
        video_dev->mmap_buf[i].virt_addr = (uint8_t *)mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, video_dev->fd, buffer.m.offset);
        if (video_dev->mmap_buf[i].virt_addr == MAP_FAILED) {
            MEDIA_LOG_ERROR("usb_mmap failed\n");
            usb_munmap(video_dev);
        }

        video_dev->buf_size = buffer.length;
#elif DMA_BUF

#endif

#ifdef CAM_DEBUG
        MEDIA_LOG_INFO("======== buffer info ========\n");
        MEDIA_LOG_INFO("index = %d\n", buffer.index);
        MEDIA_LOG_INFO("type = %d\n", buffer.type);
        MEDIA_LOG_INFO("memory = %d\n", buffer.memory);
        MEDIA_LOG_INFO("length = %d\n", buffer.length);
        MEDIA_LOG_INFO("sequence = %d\n", buffer.sequence);
#endif
    }

    return 0;
}

static int usb_queue_buffer(video_dev_t *video_dev) {
    int ret;
    struct v4l2_buffer buffer;

    CLEAR(buffer);
    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    for (int i = 0; i < BUF_COUNT; i++) {
        buffer.index = i;
#ifdef MMAP_BUF
        buffer.memory = V4L2_MEMORY_MMAP;
#elif DMABUF_BUF
        buffer.memory = V4L2_MEMORY_DMABUF;
        for (j = 0; j < num_planes; ++j) {
            // planes[j].length = user_bufs[i].planes[j].len;
            planes[j].m.video_dev->fd = user_bufs[i].planes[j].m.video_dev->fd;
        }
        buffer.m.planes = planes;
        buffer.length   = num_planes;
#endif
        ret = ioctl(video_dev->fd, VIDIOC_QBUF, &buffer);
        if (ret < 0) {
            MEDIA_LOG_ERROR("VIDIOC_QBUF failed\n");
            usb_munmap(video_dev);
            return -1;
        }
    }

    return 0;
}

static int usb_dev_init(video_dev_t *video_dev) {
    int ret;

    video_dev->fd = open(USB_CAM_NAME, O_RDWR);
    if (video_dev->fd < 0) {
        MEDIA_LOG_ERROR("can't open %s\n", USB_CAM_NAME);
        return -1;
    }

    ret = usb_set_format(video_dev);
    if (ret < 0)
        return -1;

    ret = usb_request_buffers(video_dev);
    if (ret < 0)
        return -1;

    ret = usb_mmap_buffers(video_dev);
    if (ret < 0)
        return -1;

    ret = usb_queue_buffer(video_dev);
    if (ret < 0)
        return -1;

    video_dev->video_opr = &usb_camera_opr;

    MEDIA_LOG_INFO("%s init success", USB_CAM_NAME);

    return 0;
}

static int usb_dev_exit(video_dev_t *video_dev) {
    int ret;

    ret = close(video_dev->fd);
    if (ret < 0) {
        MEDIA_LOG_ERROR("failed to close usb dev\n");
        return -1;
    }

    usb_munmap(video_dev);

    MEDIA_LOG_INFO("%s exit", USB_CAM_NAME);

    return 0;
}

static int usb_get_frmae(video_dev_t *video_dev) {
    int ret;
    struct v4l2_buffer buffer;

    CLEAR(buffer);
    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

#ifdef MMAP_BUF
    buffer.memory = V4L2_MEMORY_MMAP;
#elif DMA_BUF
    buffer.memory = V4L2_MEMORY_DMABUF;
#endif
    ret = ioctl(video_dev->fd, VIDIOC_DQBUF, &buffer);
    if (ret < 0) {
        MEDIA_LOG_ERROR("VIDIOC_DQBUF failed\n");
        return -1;
    }

    video_dev->index = buffer.index;
    // video_dev->buf_size = buffer.bytesused;

    return 0;
}

static int usb_put_frame(video_dev_t *video_dev) {
    int ret;
    struct v4l2_buffer buffer;

    CLEAR(buffer);
    buffer.index = video_dev->index;
    buffer.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;

#ifdef MMAP_BUF
    buffer.memory = V4L2_MEMORY_MMAP;
#elif DMA_BUF
    buffer.memory = V4L2_MEMORY_DMABUF;
#endif

    ret = ioctl(video_dev->fd, VIDIOC_QBUF, &buffer);
    if (ret < 0) {
        MEDIA_LOG_ERROR("VIDIOC_QBUF failed\n");
        return -1;
    }

    return 0;
}

static int usb_dev_start(video_dev_t *video_dev) {
    int ret;
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    ret = ioctl(video_dev->fd, VIDIOC_STREAMON, &type);
    if (ret < 0) {
        MEDIA_LOG_ERROR("VIDIOC_STREAMON failed\n");
        return -1;
    }

    MEDIA_LOG_INFO("%s start...", USB_CAM_NAME);

    return 0;
}

static int usb_dev_stop(video_dev_t *video_dev) {
    int ret;
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    ret = ioctl(video_dev->fd, VIDIOC_STREAMOFF, &type);
    if (ret < 0) {
        MEDIA_LOG_ERROR("VIDIOC_STREAMOFF failed\n");
        return -1;
    }

    MEDIA_LOG_INFO("%s stop", MIPI_CAM_NAME);

    return 0;
}

static video_opr_t usb_camera_opr = {
    .name         = USB_CAM_NAME,
    .video        = NULL,
    .dev_init     = usb_dev_init,
    .dev_exit     = usb_dev_exit,
    .query_cap    = usb_query_capability,
    .enum_formats = usb_enum_formats,
    .set_format   = usb_set_format,
    .get_format   = usb_get_format,
    .get_frame    = usb_get_frmae,
    .put_frame    = usb_put_frame,
    .dev_start    = usb_dev_start,
    .dev_stop     = usb_dev_stop,
};

void usb_cam_register(video_dev_t *video) {
    usb_camera_opr.video = video;
    register_video(&usb_camera_opr);
    MEDIA_LOG_INFO("usb camera module register success");
}
